package ldh.im.fxgui.controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import ldh.im.ImConfig;
import ldh.im.client.CommonLocalSeqCreator;
import ldh.im.dto.business.user.LoginReq;
import ldh.im.dto.business.user.LoginResp;
import ldh.im.dto.business.user.LoginTokenReq;
import ldh.im.exception.BusinessException;
import ldh.im.fxbase.dao.ImLoginDao;
import ldh.im.fxbase.data.pojo.ImLogin;
import ldh.im.fxbase.data.pojo.status.ImLoginType;
import ldh.im.fxbase.util.*;
import ldh.im.fxbase.cell.ObjectListCell;
import ldh.im.fxbase.fxanimations.FadeOutTransition;
import ldh.im.fxbase.util.MovableHelper;
import ldh.im.fxbase.rpc.service.RpcLoginService;
import ldh.im.fxgui.util.UiUtil;
import ldh.im.util.ConfigUtil;
import ldh.rpc.CodecType;
import ldh.rpc.TerminalType;
import ldh.rpc.cache.CacheService;
import ldh.rpc.client.RpcClientService;
import ldh.rpc.codec.RpcStupidBearBinaryCodec;
import ldh.rpc.dto.CodecReq;
import ldh.rpc.exception.RpcException;
import ldh.rpc.util.JsonUtil;
import org.kordamp.ikonli.fontawesome.FontAwesome;
import org.kordamp.ikonli.javafx.FontIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URL;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.Future;
import java.util.function.Consumer;

/***
 * 登录
 */
public class LoginController implements Initializable {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    @FXML public Button closeBtn;
    @FXML public ComboBox<ImLogin> userNameComboBox;
    @FXML public PasswordField passwordField;
    @FXML public Pane contentPane;
    @FXML public StackPane root;
    @FXML private Button loginBtn;
    @FXML private ProgressIndicator loginProgress;
    @FXML private Label errorLoginTip;
    @FXML private CheckBox rememberMe;

    private ImLoginType loginType = ImLoginType.Password;

    private Future loginFuture = null;

    private Consumer<?> callBack = null;

    private Stage loginStage = null;
    private boolean isCancel = false;
    private boolean isNoPasswordLogin = false;  // 是否无密登录
    private ImLoginDao imLoginDao = BeanFactory.getInstance().getBean(ImLoginDao.class);

    public Scene createScene() {
        Scene scene = new Scene(root, 450, 330);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add(this.getClass().getResource("/css/var.css").toExternalForm());
        scene.getStylesheets().add(this.getClass().getResource("/css/login.css").toExternalForm());
        scene.getStylesheets().add(this.getClass().getResource("/css/bootstrapfx.css").toExternalForm());
        scene.getStylesheets().add(this.getClass().getResource("/css/scrollbar.css").toExternalForm());
        return scene;
    }

    public void setLoginStage(Stage stage) {
        this.loginStage = stage;
    }

    @FXML
    public void close() {
        close(null, (Void)->{
            StageUtil.close();
            loginStage = null;
            return null;
        });
    }

    @FXML
    public void loginBtn() {
        if(!checkForm()) {
            return;
        }
        loginBtn.setDisable(true);
        loginProgress.setVisible(true);
        errorLoginTip.setText("登录中......");
        isCancel = false;
        String userId = userNameComboBox.getValue().getUserId();
        String password = passwordField.getText();
        Object userData = passwordField.getUserData();
        String token = userNameComboBox.getValue().getToken();
        loginFuture = ThreadPoolUtil.submit(()->{
            boolean isLoginSuccess = loginAction(userId, password, userData, token);
            LOGGER.info("login result: {}", isLoginSuccess);
            if (!isLoginSuccess) return;
            if (isCancel) {
                resetLogin(null);
                return;
            }
            LOGGER.info("start load data");
            FxThreadUtil.runLater(()->errorLoginTip.setText("加载数据中......"));

            if (callBack != null) {
                callBack.accept(null);
                callBack = null;
            }

            LOGGER.info("begin show main page");
            FxThreadUtil.runLater(()->{
                close((Void)->{}, null);
            });
        }, (errorMsg)->{
            LOGGER.error("登录异常", errorMsg);
            String errorTip = isCancel ? "登录被取消" : "登录错误";
            resetLogin(errorTip);
        });
    }

    @FXML
    public void cancelAct() {
        isCancel = true;
        if (loginFuture != null) loginFuture.cancel(true);
        loginBtn.setDisable(false);
        loginProgress.setVisible(false);
        errorLoginTip.setText("登录被取消");
    }

    private void resetLogin(String loginErrorMsg) {
        loginFuture = null;
        isCancel = false;
        String errorTip = loginErrorMsg != null ? loginErrorMsg : "";
        FxThreadUtil.runLater(()-> {
            loginBtn.setDisable(false);
            errorLoginTip.setText(errorTip);
            loginProgress.setVisible(false);
        });
    }

    private boolean loginAction(String userId, String password, Object userData, String token) {
        ImLogin imLogin = new ImLogin();
        imLogin.setUserId(userId);

        try {
            CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
            RpcLoginService rpcLoginService = RpcUtil.getRpcBootstrap().getRpcConfig().getClientBean(RpcLoginService.class);
            RpcClientService clientService = RpcUtil.getRpcBootstrap().getRpcConfig().getClientBean(RpcClientService.class);

            LoginResp loginResp = null;
            long time = System.currentTimeMillis();
            CodecType codecType = RpcUtil.getRpcBootstrap().getRpcConfig().getClientCodecType();
            cacheService.setCache(ImConfig.CODEC_TYPE, codecType, null);
            clientService.codecType(new CodecReq(codecType));
            LOGGER.info("login time1: {}", System.currentTimeMillis() - time);
            if (isNoPasswordLogin && userData != null && userData.toString().equals(password)) {
                LoginTokenReq loginReq = new LoginTokenReq(userId, token, TerminalType.pc);
                loginReq.setTenantCode(ConfigUtil.getString("im.tenantCode"));
                loginReq.setMachineCode(MacUtils.generateMachineKey());
                loginResp = rpcLoginService.loginByToken(loginReq);
                LOGGER.info("login time2: {}", System.currentTimeMillis() - time);
            } else {
                LoginReq loginReq = new LoginReq(userId, password, TerminalType.pc);
                loginReq.setTenantCode(ConfigUtil.getString("im.tenantCode"));
                loginReq.setMachineCode(MacUtils.generateMachineKey());
                RpcStupidBearBinaryCodec codec = new RpcStupidBearBinaryCodec();
                byte[] datas = codec.encode(loginReq);
                LOGGER.info("login size: {}, {}", datas.length, JsonUtil.toJson(loginReq));
                loginResp = rpcLoginService.login(loginReq);
            }
            if (loginResp.isSuccess()) {
                cacheService.setCache(ImConfig.LOGIN_USER_ID, loginResp.getUserId(), null);
                cacheService.setCache(ImConfig.TENANT_ID, loginResp.getTenantCode(), null);
                cacheService.setCache(ImConfig.AUTH_TOKEN, loginResp.getAuthToken(), null);
                cacheService.setCache(ImConfig.AUTH_CODE, loginResp.getAuthCode(), null);
                cacheService.setCache("serverDate", loginResp.getServerDateTime(), null);

                UiUtil.setServerDateTime(loginResp.getServerDateTime());
                RpcUtil.getRpcBootstrap().getRpcConfig().setLocalSeqCreator(new CommonLocalSeqCreator(loginResp.getUserId(), loginResp.getServerDateTime()));
                imLogin.setToken(loginResp.getToken());
                imLogin.setLoginType(loginType);
                imLogin.setMaskPassword(buildMaskPassword(password));
                imLogin.setUpdateTime(new Date());
                saveCacheLoginUser(imLogin);
                long loginTime = System.currentTimeMillis() - time;
                LOGGER.info("login time: {}", loginTime);
                return true;
            } else {
                String errorMsg = loginResp.getOtherInfo() != null ? loginResp.getOtherInfo() : "系统错误" ;
                FxThreadUtil.runLater(()->{
                    errorLoginTip.setText(errorMsg);
                    loginBtn.setDisable(false);
                    loginProgress.setVisible(false);
                });
            }
        } catch (Exception e) {
            LOGGER.error("登录错误", e);
            FxThreadUtil.runLater(()->{
                userNameComboBox.setEditable(true);
                if (e instanceof RpcException) {
                    RpcException rpcException = (RpcException) e;
                    String errorMessage = rpcException.getMessage();
                    int idx = errorMessage.lastIndexOf(":");
                    if (idx > 0) {
                        errorMessage = errorMessage.substring(idx+1);
                    }
                    errorLoginTip.setText(errorMessage);
                } else if (e instanceof BusinessException) {
                    if (e.getCause() != null) {
                        passwordField.setText("");
                        errorLoginTip.setText(e.getCause().getMessage());
                    } else {
                        errorLoginTip.setText(e.getMessage());
                    }
                } else if (e.getCause() instanceof BusinessException) {
                    errorLoginTip.setText(e.getCause().getMessage());
                } else {
                    errorLoginTip.setText("连接失败");
                }
                loginBtn.setDisable(false);
                loginProgress.setVisible(false);
            });
        }
        return false;
    }

    private String buildMaskPassword(String password) {
        String maskPassword = "";
        for (int i=0; i<password.length(); i++) {
            maskPassword += "1";
        }
        return maskPassword;
    }

    private void saveCacheLoginUser(ImLogin imLogin) throws SQLException {
        if (!rememberMe.isSelected()) {
            imLogin.setMaskPassword("");
            imLogin.setToken("");
            imLoginDao.updateLogin(imLogin);
            return;
        }
        ImLogin existed = imLoginDao.getLoginByUserId(imLogin.getUserId());
        if (existed == null) {
            imLoginDao.insertLogin(imLogin);
        } else {
            imLogin.setUpdateTime(new Date());
            imLoginDao.updateLogin(imLogin);
        }
    }

    private boolean checkForm() {
        if (userNameComboBox.getValue() == null || StringUtil.isEmpty(userNameComboBox.getValue().getUserId())) {
            errorLoginTip.setText("用户名不能为空");
            return false;
        }
        if (StringUtil.isEmpty(passwordField.getText())) {
            errorLoginTip.setText("密码不能为空");
            return false;
        }
        return true;
    }

    public Pane getRoot() {
        return root;
    }

    public void setCallBack(Consumer<?> callBack) {
        this.callBack = callBack;
    }

    private void close(Consumer<?> closeConsumer, Callback callback) {
        FadeOutTransition fadeOutTransition = new FadeOutTransition(root);
        fadeOutTransition.setOnFinished(e->{
            if (callback != null) callback.call(null);
//            if(loginStage != null) loginStage.close();
            if (closeConsumer != null) {
                closeConsumer.accept(null);
            }
        });
        fadeOutTransition.playFromStart();
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        MovableHelper.movable(contentPane);

        Rectangle clip = new Rectangle();
        clip.widthProperty().bind(root.widthProperty());
        clip.heightProperty().bind(root.heightProperty());
        clip.setArcWidth(15);
        clip.setArcHeight(15);
        root.setClip(clip);

        userNameComboBox.setConverter(new StringConverter<ImLogin>() {

            @Override
            public String toString(ImLogin imLogin) {
                if(imLogin == null) return "";
                String u = imLogin.getUserId();
                return u;
            }

            @Override
            public ImLogin fromString(String s) {
                if (s != null && !s.trim().equals("")) {
                    List<ImLogin> imLoginList = userNameComboBox.getItems();
                    Optional<ImLogin> loginPoOptional = imLoginList.stream().filter(l->l.getUserId().equals(s)).findFirst();
                    if (loginPoOptional.isPresent()) return loginPoOptional.get();
                    ImLogin imLogin = new ImLogin();
                    imLogin.setUserId(s);
                    return imLogin;
                }
                return null;
            }
        });

        userNameComboBox.getSelectionModel().selectedItemProperty().addListener((ob, o, n)->{
            if (userNameComboBox.getSelectionModel().getSelectedItem() == null) {
                return;
            }
            String password = userNameComboBox.getSelectionModel().getSelectedItem().getMaskPassword();
            if (password == null) password = "";
            if (!password.equals("")) {
                userNameComboBox.setEditable(false);
                passwordField.setUserData(password);
            }
            if (o == n) return;
//            if(userNameComboBox.isEditable()) return;
            passwordField.setText(password);
            if (password.equals("") && userNameComboBox.getValue().getUserId().equals("")) {
                if (userNameComboBox.isEditable()) return;
                passwordField.setUserData(null);
                isNoPasswordLogin = false;
                loginType = ImLoginType.Password;
                passwordField.requestFocus();
                userNameComboBox.setEditable(true);
                userNameComboBox.requestFocus();
            } else {
                isNoPasswordLogin = true;
//                userNameComboBox.setEditable(false);
                passwordField.requestFocus();
            }
        });

        userNameComboBox.setCellFactory(new ObjectListCell<ImLogin>(imLogin ->{
            Label label = new Label();
            label.setPadding(new Insets(0, 0, 0, 10));
            label.setText(imLogin.getUserId());
            FontIcon fontIcon = FontIcon.of(FontAwesome.findByDescription("fa-user"));
            if (imLogin.getUserId().equals("")) {
                fontIcon = FontIcon.of(FontAwesome.findByDescription("fa-user-plus"));
            }

            label.setGraphic(fontIcon);
            return label;
        }));

        ImLogin nullImLogin = new ImLogin();
        nullImLogin.setMaskPassword("");
        nullImLogin.setUserId("");
        userNameComboBox.getItems().addAll(nullImLogin);

        ThreadPoolUtil.submit(()->{
            try {
                List<ImLogin> imLoginList = imLoginDao.getLogins();
                ImLogin lastedLogin = getLastedLogin(imLoginList);
                FxThreadUtil.runLater(()-> {
                    userNameComboBox.getItems().addAll(imLoginList);
                    if (lastedLogin != null) {
                        loginType = ImLoginType.Token;
                        userNameComboBox.getSelectionModel().select(lastedLogin);
                    }
                    loginBtn.requestFocus();
                });
            } catch (Exception e) {
                LOGGER.error("获取无秘登录用户失败", e);
            }
        });
    }

    private ImLogin getLastedLogin(List<ImLogin> imLoinList) {
        if (imLoinList.size() < 1) return null;
        return imLoinList.stream().sorted((l1, l2)->l2.getUpdateTime().compareTo(l1.getUpdateTime())).findFirst().get();
    }
}
